package com.code.generation.utils;

import com.code.generation.constant.TableConstants;
import com.code.generation.vo.db.DataBaseVO;
import com.code.generation.vo.table.Column;
import com.code.generation.vo.table.Table;
import org.apache.commons.lang3.StringUtils;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * @ProjectName: codegeneration
 * @Package: com.code.generation.utils
 * @Description:
 * @Author: DongJW
 * @CreateDate: 2020/11/29 11:39
 * @Version: 1.0
 */
public class JDBCUtil {
    /**
     * 1.
     * 获取jdbc连接的方法getconnection （通过JDBCUtil.getConnection（）来获取一个JDBC的连接）
     * ip 为数据库所在的远程服务器的ip地址
     * port 为数据库访问的端口
     * mysqldatabase  要连接的数据库名称
     */
    public static Connection getConnection(DataBaseVO dataBaseVO) throws Exception{

        //1加载驱动
        Class.forName(dataBaseVO.getDriver());
        //String url = "jdbc:mysql://ip:port/数据库名称";(port通常是3306，ip需输入，数据库名称需输入，用户名密码为PHPMyAdmin的进入密码)
        //2.获取连接，url为jdbc连接：连接的mysql，服务器ip+数据库的名称   再由驱动管理者获取连接（url，username，password）
        try {
            return DriverManager.getConnection(dataBaseVO.getUrl(), dataBaseVO.getUsername(), dataBaseVO.getPassword());
        } catch (SQLException throwables) {
            throw new Exception("创建数据链接失败，请检查数据库相关信息  "+throwables);
        }
    }

    /**
     * 获取表信息
     * @param connection 数据库连接
     * @return 表信息
     * @throws Exception 异常信息
     */
    public static List<Table> getTableInfo(Connection connection) throws Exception{
        List<Table> tableList = new ArrayList<>();
        //获取数据库所有表
        //获取DatabaseMetaData
        DatabaseMetaData metaData = connection.getMetaData();
        try (ResultSet tables = metaData.getTables(null, null, null, new String[]{"TABLE"})) {
            while (tables.next()) {
                Table table = new Table();
                //获取表名
                String tableName = tables.getString("TABLE_NAME");
                table.setTableName(tableName);

                //处理表名（这个名字将来要用作类名），先从配置文件读取需要去除的表前缀
                String tableNameExt = processTableName(tables.getString("TABLE_NAME"));
                table.setTableNameExt(tableNameExt);

                //首字母小写
                StringBuilder stringBuilder = new StringBuilder();
                stringBuilder.append((tableNameExt.substring(0, 1)).toLowerCase()).append((tableNameExt.substring(1)));
                String htmlTableNameExt = stringBuilder.toString();
                table.setTableNameExt1(htmlTableNameExt);

                //描述
                table.setComment(tables.getString("REMARKS"));

                //主键
                ResultSet primaryKeys = metaData.getPrimaryKeys(null, null, tableName);
                String key = "";
                //复合主键的情况不做处理了，默认就是一个主键了
                while (primaryKeys.next()) {
                    key = primaryKeys.getString("COLUMN_NAME");
                }
                table.setPrimaryKey(key);

                //处理表中的所有字段
                List<Column> columnsList = processTableColumn(metaData, tableName, key, table);
                table.setColumns(columnsList);

                //实体类型集合
                Set<String> collect = columnsList.stream().map(Column::getColumnType).collect(Collectors.toSet());
                table.setColumnTypeSet(collect);

                tableList.add(table);
            }

        }catch (Exception e){
            throw new Exception("获取数据库表信息失败，请检查表结构是否正确 "+e);
        }
        return tableList;
    }


    /**
     * 处理表中字段
     * @param metaData metaData
     * @param tableName tableName
     * @param key key
     * @param table 表信息
     * @return 列信息
     * @throws Exception 异常信息
     */
    private static List<Column> processTableColumn(DatabaseMetaData metaData, String tableName, String key,Table table)throws Exception {
        List<Column> list = new ArrayList<>();
        //这么写资源可以自己关闭
        try (ResultSet columns = metaData.getColumns(null, null, tableName, null)) {
            while (columns.next()) {
                Column column = new Column();
                //列名
                String columnName = columns.getString("COLUMN_NAME");
                column.setColumnName(columnName);

                //构造属性名称
                String attr = processColumnName(columnName);
                column.setColumnNameExt(attr);

                //javabean属性类型和数据库类型
                String dbType = columns.getString("TYPE_NAME");
                if ("DATETIME".equals(dbType)) {
                    //不这么处理生成的mapper.xml文件会有错误
                    dbType = "TIMESTAMP";
                } else if ("INT".equals(dbType)) {
                    dbType = "INTEGER";
                }
                column.setColumnDbType(dbType);
                String javaType = PropertitesUtil.propsMap.get(dbType);
                if ("BIGINT UNSIGNED".equals(dbType)) {
                    javaType = "Long";
                    column.setColumnDbType("BIGINT");
                } else if ("TINYINT UNSIGNED".equals(dbType)) {
                    javaType = "Integer";
                    column.setColumnDbType("TINYINT");
                }
                column.setColumnType(javaType);
                //匹配不到对应的java类型
                if (StringUtils.isEmpty(column.getColumnType())) {
                    System.out.println("-----------------------------------------------------");
                    System.out.println("表: " + tableName + " 字段：" + columnName +" 数据库类型："+columns.getString("TYPE_NAME")+ " 匹配不到对应的java类型");
                }

                //备注
                String columnComment = columns.getString("REMARKS");
                column.setColumnComment(columnComment);
                //是否主键
                column.setIsPk(columnName.equals(key) ? "1" : "0");
                if ("1".equals(column.getIsPk())) {
                    table.setPrimaryKeyType(column.getColumnType());
                    table.setPrimaryKeyExt(column.getColumnNameExt());
                    table.setPrimaryKeyTypePath(PropertitesUtil.propsMap.get(javaType));
                }

                list.add(column);
            }
        }
        return list;
    }

    /**
     * 处理列名
     *
     * @param columnName 列名
     * @return 处理后的列名
     */
    public static String processColumnName(String columnName) {
        if (StringUtils.isEmpty(columnName)) {
            return "";
        }
        StringBuilder s1 = new StringBuilder();
        String[] s = columnName.split("_");
        if (s.length > 1) {
            for (String value : s) {
                //后面部分首字母大写
                s1.append(value.substring(0, 1).toUpperCase());
                if (value.length() > 1) {
                    s1.append(value.substring(1));
                }
            }
        } else {
            s1.append(columnName);
        }
        //不管是什么样数据，首字母处理成小写
        String s2 = s1.toString();
        return s2.length() > 1 ? s2.substring(0, 1).toLowerCase() + s2.substring(1) : s2.substring(0, 1).toLowerCase();
    }

    /**
     * 处理原始表名
     *
     * @param tableName 原始表名称
     * @return 处理后的表名
     */
    private static String processTableName(String tableName) {
        if (StringUtils.isEmpty(tableName)) {
            return "";
        }
        //获取需要处理的表前缀
        String tablePrefixs = PropertitesUtil.propsMap.get(TableConstants.PROPER_TABLE_PREFIX);
        if (!StringUtils.isEmpty(tablePrefixs)) {
            String[] tablePrefixArry = tablePrefixs.split(",");
            for (String ablePrefix : tablePrefixArry) {
                if (tableName.contains(ablePrefix)) {
                    tableName.replace(ablePrefix, "");
                }
            }
        }

        //去除剩余的下划线，将所有表名的首字母改成大写
        String[] tableNameArry = tableName.split("_");
        StringBuilder stringBuilder = new StringBuilder();
        for (String table : tableNameArry) {
            stringBuilder.append(table.substring(0, 1).toUpperCase()).append(table.substring(1));
        }
        return stringBuilder.toString();
    }

    /**
     * 2.
     * 关闭，释放资源的方法close （若不存在使用下列资源，传递参数为null即可，通过JDBCUtil.close()关闭资源）
     * rs 为结果集，通过JDBC查到的结果集，使用后需关闭释放资源
     * stmt 为开启的sql语句
     * connection 为jdbc的连接
     */
    public static void close(ResultSet rs, PreparedStatement ps, Statement stmt, Connection connection) {//栈式关闭（最先连接，最后关闭连接）
        try {//关闭结果集
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {//关闭sql语句
            if (ps != null) {
                ps.close();
            }
            if (stmt != null) {
                stmt.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }

        try {//关闭连接
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public static void close(Connection connection) {
        try {//关闭连接
            if (connection != null) {
                connection.close();
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}
